// 2015年版权所有etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package transport

import (
	"crypto/tls"
	"fmt"
	"net"
	"time"
)

type keepAliveConn interface {
	SetKeepAlive(bool) error
	SetKeepAlivePeriod(d time.Duration) error
}

// NewKeepAliveListener返回侦听给定地址的侦听器。
// 如果TLSInfo不是nil，则与另一个侦听器环绕KeepAlivelListener时要小心。
// 某些pkg（如go/http）可能希望侦听器返回TLSConn类型以启动TLS握手。
// http:
func NewKeepAliveListener(l net.Listener, scheme string, tlscfg *tls.Config) (net.Listener, error) {
	if scheme == "https" {
		if tlscfg == nil {
			return nil, fmt.Errorf("cannot listen on TLS for given listener: KeyFile and CertFile are not presented")
		}
		return newTLSKeepaliveListener(l, tlscfg), nil
	}

	return &keepaliveListener{
		Listener: l,
	}, nil
}

type keepaliveListener struct{ net.Listener }

func (kln *keepaliveListener) Accept() (net.Conn, error) {
	c, err := kln.Listener.Accept()
	if err != nil {
		return nil, err
	}
	kac := c.(keepAliveConn)
	// 检测时间：tcp_keepalive_time+tcp_keepalive_probe+tcp_keepalive_intvl 
	// linux上的默认值：30+8*30 
	// osx上的默认值：30+8*75 
	kac.SetKeepAlive(true)
	kac.SetKeepAlivePeriod(30 * time.Second)
	return c, nil
}

// tlsKeepaliveListener为TLS连接实现网络侦听器（net.listener）。
type tlsKeepaliveListener struct {
	net.Listener
	config *tls.Config
}

// 接受等待并返回下一个传入的TLS连接。
// 返回的连接c是*tls。Conn.
func (l *tlsKeepaliveListener) Accept() (c net.Conn, err error) {
	c, err = l.Listener.Accept()
	if err != nil {
		return
	}
	kac := c.(keepAliveConn)
	// 检测时间：tcp_keepalive_时间+tcp_keepalive_探测器+tcp_keepalive_intvl 
	// linux上的默认值：30+8*30 
	// osx上的默认值：30+8*75 
	kac.SetKeepAlive(true)
	kac.SetKeepAlivePeriod(30 * time.Second)
	c = tls.Server(c, l.config)
	return c, nil
}

// 新建侦听器创建一个侦听器，该侦听器接受来自内部
// 侦听器的连接并对每个侦听器进行包装与服务器的连接。
// 配置必须为非零，并且必须具有
// 至少一个证书。
func newTLSKeepaliveListener(inner net.Listener, config *tls.Config) net.Listener {
	l := &tlsKeepaliveListener{}
	l.Listener = inner
	l.config = config
	return l
}
